#!/usr/bin/env python
#
# Copyright (C) 2005-2020 ABINIT Group (Yann Pouillon)
#
# This file is part of the ABINIT software package. For license information,
# please see the COPYING file in the top-level directory of the ABINIT source
# distribution.
#
from __future__ import print_function, division, absolute_import #, unicode_literals

try:
    from ConfigParser import ConfigParser
except ImportError:
    from configparser import ConfigParser
from time import gmtime,strftime

try:
    from commands import getoutput
except:
    from subprocess import getoutput
import os
import re
import sys

class MyConfigParser(ConfigParser):

  def optionxform(self,option):
    return str(option)

# ---------------------------------------------------------------------------- #

#
# Subroutines
#

# Macro header
def macro_header(name,stamp):

  return """# Generated by %s on %s

#
# Command-line options for the "configure" script
#

#
# IMPORTANT NOTE
#
# This file has been automatically generated by the %s
# script. If you try to edit it, your changes will systematically be
# overwritten.
#
""" % (name,stamp,name)



# Define macro header
def macro_define_header():

  return """


# ABI_OPTIONS_DEFINE()
# --------------------
#
# Declares command-line arguments for the "configure" script.
#
AC_DEFUN([ABI_OPTIONS_DEFINE],[
  # Count use of deprecated options
  abi_opt_deprecated_count=0
  abi_opt_deprecated_used=""

"""



# Define macro footer
def macro_define_footer():

  return "]) # ABI_OPTIONS_DEFINE\n"



# Init macro header
def macro_setup_header():

  return """


# ABI_OPTIONS_SETUP()
# -------------------
#
# Sets the default values of command-line arguments.
#
AC_DEFUN([ABI_OPTIONS_SETUP],[
"""



# Init macro footer
def macro_setup_footer():

  return "]) # ABI_OPTIONS_SETUP\n"



# Backup macro header
def macro_backup_header():

  return """


# ABI_OPTIONS_BACKUP()
# --------------------
#
# Saves all command-line arguments.
#
AC_DEFUN([ABI_OPTIONS_BACKUP],[
"""



# Backup macro footer
def macro_backup_footer():

  return "]) # ABI_OPTIONS_BACKUP\n"



# Restore macro header
def macro_recall_header():

  return """


# ABI_OPTIONS_RECALL()
# --------------------
#
# Restores all previously-saved command-line arguments.
#
AC_DEFUN([ABI_OPTIONS_RECALL],[
"""



# Restore macro footer
def macro_recall_footer():

  return "]) # ABI_OPTIONS_RECALL\n"



# Changed options macro
def macro_changed_template():

  return """


# ABI_INFO_OPTIONS_CHANGED()
# --------------------------
#
# Display changes of user interface between versions and warns about
# obsolete uses.
#
AC_DEFUN([ABI_INFO_OPTIONS_CHANGED],[
@MACRO@
]) # ABI_INFO_OPTIONS_CHANGED
"""



# Parsing macro
def macro_parse_template():

  return """


# ABI_OPTIONS_PARSE()
# -------------------
#
# Parses command-line arguments.
#
AC_DEFUN([ABI_OPTIONS_PARSE],[
  AC_REQUIRE([AC_PROG_EGREP])
  AC_MSG_NOTICE([parsing command-line options])
@MACRO@
]) # ABI_OPTIONS_PARSE
"""



# Defines and conditionals
def macro_triggers_template():

  return """


# ABI_OPTIONS_CPP_DEFINES()
# -------------------------
#
# Set switches associated to 'enable_*' options (AC_DEFINE and
# AM_CONDTIONAL).
#
AC_DEFUN([ABI_OPTIONS_CPP_DEFINES],[
  AC_MSG_NOTICE([setting build switches associated to command-line options])
@MACRO@
]) # ABI_OPTIONS_CPP_DEFINES
"""



# Config file values
def macro_cfg_template():

  return """


# ABI_OPTIONS_CFG_TRANSFER()
# --------------------------
#
# Transfer options from the config file to internal variables.
#
AC_DEFUN([ABI_OPTIONS_CFG_TRANSFER],[
@MACRO@
]) # ABI_OPTIONS_CFG_TRANSFER
"""



# Option parser generator
def parse_opt(opt,opt_ivar,values):

  ret = "  # Parse %s\n" % (opt)

  if ( len(values.split()) > 1 ):
    ret += """  if test "${%s}" != ""; then
    for v in `echo "${%s}" | sed -e 's/+/ /g'`; do
      opt_ok="no"
      for r in %s; do
        if test "${v}" = "${r}";then
          opt_ok="yes"
          break
        fi
      done
      test "${opt_ok}" = "no" && break
    done
    if test "${opt_ok}" = "no"; then
      AC_MSG_WARN([%s = (%s)])
      AC_MSG_ERROR([invalid option: %s = ${v}])
    fi
  fi
""" % (opt_ivar,opt_ivar,values,opt,"|".join(values.split()),opt)

  else:

    if ( values == "@float" ):
      ret += """ if test "${%s}" != ""; then
    opt_ok="no"
    test "`echo "${%s}" | \\
      ${EGREP} -e '^-?[[0-9]]+\.[[0-9]]+$'`" != "" && opt_ok="yes"
    test "`echo "${%s}" | \\
      ${EGREP} -e '^-?[[0-9]]+\.[[0-9]]+[[Ee]]-?[[0-9]]+$'`" != "" && opt_ok="yes"
    if test "${opt_ok}" = "no"; then
      AC_MSG_ERROR([invalid float: %s = ${%s}])
    fi
  fi
""" % (opt,opt,opt,opt,opt)

    elif ( values == "@includes" ):
      ret += """ if test "${%s}" != ""; then
    opt_ok="yes"
    for v in ${%s}; do
      if test "`echo "${v}" | grep '^-I'`" = ""; then
        opt_ok="no"
        break
      fi
    done
    if test "${opt_ok}" = "no"; then
      AC_MSG_ERROR([invalid include statement in %s: ${v}])
    fi
  fi
""" % (opt,opt,opt)

    elif ( values == "@install" ):
      ret += """ if test "${%s}" != ""; then
    opt_ok="yes"
    if test "${%s}" != "no" -a "${%s}" != "yes"; then
      if test \! -d "${%s}" -o \! -r "${%s}"; then
        opt_ok="no"
        break
      fi
    fi
  fi
""" % (opt,opt,opt,opt,opt)

    elif ( values == "@integer" ):
      ret += """ if test "${%s}" != ""; then
    opt_ok="yes"
    test "`echo "${%s}" | ${EGREP} -e '^-?[[0-9]]+$'`" = "" && opt_ok="no"
    if test "${opt_ok}" = "no"; then
      AC_MSG_ERROR([invalid integer: %s = ${%s}])
    fi
  fi
""" % (opt,opt,opt,opt)

    elif ( values == "@libs" ):
      ret += """ if test "${%s}" != ""; then
    opt_ok="yes"
    for v in ${%s}; do
      if test "`echo "${v}" | grep '^-[[LloW]]'`" = ""; then
        opt_ok="no"
      fi
      if test "${opt_ok}" = "no"; then
        if test -s "${v}"; then
          opt_ok="yes"
        else
          break
        fi
      fi
    done
    if test "${opt_ok}" = "no"; then
      AC_MSG_WARN([possible invalid library statement in %s: ${v}])
    fi
  fi
""" % (opt,opt,opt)

    elif ( re.match("@profile", values) ):
      arg = " ".join(values.split()[1:])
      arg = re.sub("@VALUE@", "${%s}" % opt, arg)
      ret += """ if test "${%s}" != ""; then
    opt_ok="no"
    test -s "${%s}" && opt_ok="yes"
    if test "${opt_ok}" = "no"; then
      AC_MSG_ERROR([file for option %s not found:
                  %s])
    fi
  fi
""" % (opt,arg,opt,arg)

    elif ( values == "@readabledir" ):
      ret += """ if test "${%s}" != ""; then
    opt_ok="yes"
    if test \! -d "${%s}" -o \! -r "${%s}"; then
      opt_ok="no"
      break
    fi
  fi
""" % (opt,opt,opt)

    else:

      ret += """  # FIXME: NOT IMPLEMENTED!\n
  AC_MSG_WARN([parsing not implemented for value type '%s'])\n""" % values

  return ret



# ---------------------------------------------------------------------------- #

#
# Main program
#

# Initial setup
my_name    = "make-macros-options"
my_configs = {
  "deps":"config/specs/dependencies.conf",
  "opts":"config/specs/options.conf",
}
my_output  = "config/m4/auto-options.m4"

# Check if we are in the top of the ABINIT source tree
if ( not os.path.exists("configure.ac") or
     not os.path.exists("src/98_main/abinit.F90") ):
  print("%s: You must be in the top of an ABINIT source tree." % my_name)
  print("%s: Aborting now." % my_name)
  sys.exit(1)

# Read config file(s)
cnf_deps = MyConfigParser()
cnf_opts = MyConfigParser()
for cnf_file in my_configs.values():
  if ( not os.path.exists(cnf_file) ):
    print("%s: Could not find config file (%s)." % (my_name,cnf_file))
    print("%s: Aborting now." % my_name)
    sys.exit(2)

# What time is it?
now = strftime("%Y/%m/%d %H:%M:%S +0000",gmtime())

# Init
cnf_deps.read(my_configs["deps"])
cnf_opts.read(my_configs["opts"])
re_en = re.compile("enable_")
re_wi = re.compile("with_")
all_args = cnf_opts.sections()
all_args.sort()
ac_args = { "enable":list(), "with":list() }
for arg in all_args:
  arg_stat = cnf_opts.get(arg,"status")
  if ( (arg_stat != "removed") and (arg_stat != "dropped") ):
    if ( arg.startswith("enable_") ):
      ac_args["enable"].append(arg)
    if ( arg.startswith("with_") ):
      ac_args["with"].append(arg)
ac_args["enable"].sort()
ac_args["with"].sort()

# Start writing macro
m4 = open(my_output, "wt")
m4.write(macro_header(my_name,now))

# Start writing define macro
m4.write(macro_define_header())

# Process arguments
defaults = ""
parse = ""
for arg in ("enable","with"):
  m4.write("\n  #\n  # --%s arguments\n  #\n" % (arg))
  defaults += "\n  #\n  # --%s arguments\n  #\n\n" % (arg)
  for opt in ac_args[arg]:
    var = re.sub(arg+"_","",opt)
    opt_name = re.sub("_","-",var)
    opt_desc = cnf_opts.get(opt,"description")
    opt_stat = cnf_opts.get(opt,"status")
    if ( arg == "enable" ):
      opt_ivar = "abi_%s_enable" % var
    else:
      opt_ivar = "abi_%s" % var
    try:
      opt_dflt = cnf_opts.get(opt,"default")
    except:
      opt_dflt = None
    try:
      opt_nega = cnf_opts.get(opt,"negative")
    except:
      opt_nega = ""
    try:
      opt_vals = cnf_opts.get(opt,"values")
    except:
      opt_vals = None

    if ( arg == "enable" ):
      m4.write("\n  AC_ARG_%s(%s,\n" % (arg.upper(),opt_name) \
        + "    AC_HELP_STRING([--%s-%s],\n      [%s (default: %s)]),\n" % \
          (arg,opt_name,opt_desc,opt_dflt) \
        + "    [abi_%s_enable=\"${enableval}\"; abi_%s_init=\"yon\"],\n" % (var,var) \
        + "    [abi_%s_enable=\"%s\"; abi_%s_init=\"def\"])\n" % (var, opt_dflt, var))
    else:
      tmp_dflt = ""
      if ( not opt_dflt is None ):
        tmp_dflt = opt_dflt
      tmp_init = "kwd"
      if ( opt_vals == "@install" ):
        tmp_init = "dir"
      tmp_decl = """
  AC_ARG_WITH({name},
    AC_HELP_STRING([--with-{cmd}],
      [{help} (default: '{default}')]),
    [ abi_{name}="${{withval}}"
      case "${{withval}}" in
        no|yes)
          abi_{name}_init="yon"
          ;;
        *)
          abi_{name}_init="{init}"
          ;;
      esac],
    [ abi_{name}_init="def"; abi_{name}="{default}"])
""".format(cmd=opt_name, name=var, help=opt_desc, default=tmp_dflt, init=tmp_init)
      m4.write(tmp_decl)

    if ( opt_dflt is not None ):
      if ( arg == "enable" ):
        defaults += "  if test \"${%s_%s}\" = \"\"; then\n    abi_%s_%s=\"%s\"\n  fi\n" % \
          (arg,var,var,arg,opt_dflt)
      else:
        defaults += "  if test \"${%s_%s}\" = \"\"; then\n    abi_%s=\"%s\"\n  fi\n" % \
          (arg,var,var,opt_dflt)

    if ( arg == "with" ):
      defaults += "  if test \"${%s_%s}\" = \"no\"; then\n    abi_%s=\"%s\"\n  fi\n" % \
        (arg,var,var,opt_nega)

    if ( opt_vals is not None ):
      parse += "\n"+parse_opt(opt,opt_ivar,opt_vals)
    elif ( arg == "enable" ):
      parse += "\n"+parse_opt(opt,opt_ivar,"no yes")

    m4.write("  AC_SUBST(%s_%s)\n" % (arg,var))
    if ( arg == "enable" ):
      m4.write("  AC_SUBST(abi_%s_%s)\n" % (var,arg))

# Finish writing define macro
m4.write(macro_define_footer())

# Start writing setup macro
m4.write(macro_setup_header())

# Process arguments
m4.write(defaults)

# Finish writing setup macro
m4.write(macro_setup_footer())

# Start writing backup macro
m4.write(macro_backup_header())

# Process arguments
for arg in ("enable","with"):
  m4.write("\n  #\n  # --%s arguments\n  #\n" % (arg))
  for opt in ac_args[arg]:
    var = re.sub(arg+"_","",opt)
    m4.write("  cmd_%s_%s=\"${%s_%s}\"\n" % (arg,var,arg,var))
for dep in sorted(cnf_deps.sections()):
  if (cnf_deps.get(dep, "detector") == "steredeg" ):
    m4.write("  cmd_with_%s=\"${with_%s}\"\n" % (dep,dep))
    if ( cnf_deps.has_option(dep, "flavors") ):
      m4.write("  cmd_with_%s_flavor=\"${with_%s_flavor}\"\n" % (dep,dep))

# Do not forget "prefix"
m4.write("""
  #
  # Prefix
  #
  if test "${prefix}" != "NONE"; then
    cmd_prefix="${prefix}"
  fi
""")

# Finish writing backup macro
m4.write(macro_backup_footer())

# Write transfer-from-config-file macro
transfer = ""
for arg in ("enable","with"):
  transfer += "\n  #\n  # --%s arguments\n  #\n" % (arg)
  for opt in ac_args[arg]:
    var = re.sub(arg+"_","",opt)
    if ( arg == "enable" ):
      transfer += "\n  if test \"${%s_%s}\" != \"\"; then\n    abi_%s_%s=\"${%s_%s}\"\n  fi\n" % \
        (arg,var,var,arg,arg,var)
    else:
      transfer += "\n  if test \"${%s_%s}\" != \"\"; then\n    abi_%s=\"${%s_%s}\"\n  fi\n" % \
        (arg,var,var,arg,var)
m4.write(re.sub("@MACRO@", transfer, macro_cfg_template()))

# Start writing recall macro
m4.write(macro_recall_header())

# Process arguments
for arg in ("enable","with"):
  m4.write("\n  #\n  # --%s arguments\n  #\n" % (arg))
  for opt in ac_args[arg]:
    var = re.sub(arg+"_","",opt)
    if ( arg == "enable" ):
      m4.write("  if test \"${cmd_%s_%s}\" != \"\"; then\n    abi_%s_%s=\"${cmd_%s_%s}\"\n  fi\n" % \
        (arg,var,var,arg,arg,var))
    else:
      m4.write("  if test \"${cmd_%s_%s}\" != \"\"; then\n    %s_%s=\"${cmd_%s_%s}\"\n  fi\n" % \
        (arg,var,arg,var,arg,var))
for dep in sorted(cnf_deps.sections()):
  if (cnf_deps.get(dep, "detector") == "steredeg" ):
    m4.write("  if test \"${cmd_with_%s}\" != \"\"; then\n    with_%s=\"${cmd_with_%s}\"\n  fi\n" % \
      (dep,dep,dep))
    if ( cnf_deps.has_option(dep, "flavors") ):
      m4.write("  if test \"${cmd_with_%s_flavor}\" != \"\"; then\n    with_%s_flavor=\"${cmd_with_%s_flavor}\"\n  fi\n" % \
      (dep,dep,dep))


# Do not forget "prefix"
m4.write("""
  #
  # Prefix
  #
  if test "${cmd_prefix}" != ""; then
    prefix="${cmd_prefix}"
  fi
""")

# Finish writing recall macro
m4.write(macro_recall_footer())

# Build changed macro
changed = ""
chg_dep = ""
chg_mod = ""
chg_new = ""
chg_rem = ""
chg_ren = ""

for arg in all_args:

  # Init
  arg_stat = cnf_opts.get(arg,"status")
  try:
    arg_repl = cnf_opts.get(arg,"use_instead")
  except:
    arg_repl = ""

  # Removed options
  if ( (arg_stat == "removed") or (arg_stat == "dropped") ):
    arg1 = re.sub("_","-",arg)
    chg_rem += "\n  # Removed --%s option" % (arg1)
    if ( arg_stat == "removed" ):
      chg_rem += "\n  AC_MSG_NOTICE([  * removed option --%s])" % (arg1)
      if ( arg_repl != "" ):
        if ( arg_repl.upper() == arg_repl ):
          chg_rem += "\n  AC_MSG_NOTICE([    >>> use %s instead])" % arg_repl
        else:
          chg_rem += "\n  AC_MSG_NOTICE([    >>> use --%s instead])" % \
            (re.sub("_","-",arg_repl))
    chg_rem += """
  if test "${%s}" != ""; then
    AC_MSG_ERROR([removed option --%s has been used])
  fi
""" % (re.sub("-","_",arg1),arg1)

  # New options
  elif ( arg_stat.startswith("new") ):
    arg2 = re.sub("_","-",arg.split()[0])
    chg_new += "\n  # New --%s option\n" % (arg2)
    chg_new += "  AC_MSG_NOTICE([  * new option --%s is available])\n" % (arg2)
    if ( len(arg.split()) > 1 ):
      chg_new += "  AC_MSG_NOTICE([    (%s)])\n" % (" ".join(arg.split()[1:]))

  # Changed UIs
  elif ( re.match("changed",arg_stat) ):
    arg1 = re.sub("_","-",arg)
    chg_mod += """
  # UI change for --%s option
  AC_MSG_NOTICE([  * modified option --%s])
  AC_MSG_NOTICE([    (%s)])
  AC_MSG_NOTICE([    please check that --%s="${%s}" is OK for you])
""" % (arg1,arg1,arg_stat,arg1,re.sub("-","_",arg1))

  # Deprecated options
  elif ( re.match("deprecated",arg_stat) ):
    arg2 = re.sub("_","-",arg)
    chg_dep += """
  # --%s deprecated
  AC_MSG_NOTICE([  * deprecated option --%s])
  if test "${%s}" != ""; then
    AC_MSG_NOTICE([    >>> --%s = '${%s}' will soon be forbidden!])
    abi_opt_deprecated_count=`expr ${abi_opt_deprecated_count} + 1`
    abi_opt_deprecated_used="${abi_opt_deprecated_used} %s"
  fi
""" % (arg2,arg2,arg,arg2,arg,arg)

changed = chg_rem + chg_new + chg_mod + chg_ren + chg_dep
if ( changed == "" ):
  changed = "AC_MSG_NOTICE([no change in user interface to report])"
else:
  changed = """\
  AC_MSG_NOTICE([])
  AC_MSG_NOTICE([reporting user interface changes:])
  AC_MSG_NOTICE([])\n""" + changed

# Write changed macro
m4.write(re.sub("@MACRO@",changed,macro_changed_template()))

# Write parse macro
m4.write(re.sub("@MACRO@",parse,macro_parse_template()))

# Build triggers macro
triggers = ""

for opt in ac_args["enable"]:
  opt_desc = cnf_opts.get(opt,"description")
  opt_desc = opt_desc[0].lower() + opt_desc[1:]
  opt_ivar = "abi_" + opt.replace("enable_", "") + "_enable"
  try:
    opt_cnds = cnf_opts.get(opt,"conditionals").split()
  except:
    opt_cnds = list()
  try:
    opt_defs = cnf_opts.get(opt,"defines").split()
  except:
    opt_defs = list()

  if ( len(opt_cnds) + len(opt_defs) > 0 ):
    triggers += """

  # Triggers for %s
  AC_MSG_CHECKING([whether to %s])
  AC_MSG_RESULT([${%s}])
""" % (opt,opt_desc,opt_ivar)

  if ( len(opt_cnds) > 0 ):
    for cnd in opt_cnds:
      if ( cnd[0] == "!" ):
        val = "no"
        cnd = cnd[1:]
      else:
        val = "yes"
      triggers += """
  if test "${%s}" = "%s"; then
    AC_MSG_NOTICE([triggering the '%s' conditional])
  fi
  AM_CONDITIONAL([%s],[test "${%s}" = "%s"])""" % (opt_ivar,val,cnd,cnd,opt_ivar,val)

  if ( len(opt_defs) > 0 ):
    for cpp in opt_defs:
      if ( cpp[0] == "!" ):
        val = "no"
        cpp = cpp[1:]
      else:
        val = "yes"
      triggers += """
  if test "${%s}" = "%s"; then
    AC_MSG_NOTICE([defining the '%s' preprocessing macro])
    AC_DEFINE([%s], 1,
      [Define to 1 if you want to %s.])
  fi""" % (opt_ivar,val,cpp,cpp,opt_desc)

# Write triggers macro
m4.write(re.sub("@MACRO@",triggers,macro_triggers_template()))

# Finish
m4.close()

tmp = getoutput("./config/scripts/add-header-typed Autoconf %s" % (my_output))
if ( tmp != "" ):
  print(tmp)

# Write option dumper (for debugging)
dumper = open("config.dump.in","a")
dumper.write("# Command-line options (script: %s)\n" % (my_name))
for arg in all_args:
  arg_stat = cnf_opts.get(arg,"status")
  if ( (arg_stat != "removed") and \
       (arg_stat != "dropped") and \
       (not re.match("group", arg)) ):
    var = re.sub("-","_",opt_name)
    dumper.write("%s=\"@%s@\"\n" % (arg,arg))
dumper.write("\n")
dumper.close()
